package com.gitee.deeround.jdbchelper.core;

import com.gitee.deeround.jdbchelper.core.dialect.dameng.DamengDialect;
import com.gitee.deeround.jdbchelper.core.dialect.mysql.MysqlDialect;
import com.gitee.deeround.jdbchelper.core.dialect.oracle.OracleDialect;
import com.gitee.deeround.jdbchelper.exception.DbException;
import com.gitee.deeround.jdbchelper.out.DbType;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;

public abstract class DialectHelper {
    private static final ReentrantLock lock = new ReentrantLock();
    private static String dbType = DbType.AUTO.toString();

    /**
     * key:数据库别名，value：数据库方言实现类class
     */
    private static final Map<String, Class> DIALECT_MAP = new HashMap<String, Class>();
    /**
     * key:数据库别名，value:数据库方言实现类实例
     */
    private static final Map<String, Dialect> DIALECT_INSTANCE_MAP = new HashMap<String, Dialect>();

    static {
        registerDialectAlias("MYSQL", MysqlDialect.class); // Mysql
        registerDialectAlias("ORACLE", OracleDialect.class); // Oracle
        registerDialectAlias("DM", DamengDialect.class); // 达梦
    }

    public static void registerDialectAlias(String alias, Class clazz) {
        DIALECT_MAP.put(alias.toUpperCase(), clazz);
    }

    public static void setDbType(DbType dbType1) {
        dbType = dbType1.toString();
    }

    public static void setDbType(String dbType1) {
        dbType = dbType1 == null ? dbType1 : dbType1.toUpperCase();
    }

    public static DbType getDbType() {
        return Enum.valueOf(DbType.class, dbType);
    }

    public static Dialect getDialect(JdbcTemplate jdbcTemplate) {
        String dialectName = DbType.AUTO.toString().equals(dbType) ? getDialectName(getUrl(getDataSource(jdbcTemplate))) : dbType.toString();
        if (dialectName != null) {
            if (DIALECT_INSTANCE_MAP.containsKey(dialectName)) {
                return DIALECT_INSTANCE_MAP.get(dialectName);
            }
        }
        if (dialectName != null && DIALECT_MAP.containsKey(dialectName)) {
            lock.lock();
            try {
                Class dialectClass = null;
                Dialect dialect = null;
                try {
                    dialectClass = DIALECT_MAP.get(dialectName);
                    if (Dialect.class.isAssignableFrom(dialectClass)) {
                        dialect = (Dialect) dialectClass.newInstance();
                        DIALECT_INSTANCE_MAP.put(dialectName, dialect);
                        return dialect;
                    } else {
                        throw new DbException("Dialect class must be implemented " + Dialect.class.getName() + " interface");
                    }
                } catch (Exception e) {
                    throw new DbException("Initialize dialect class [" + dialectClass + "] error : " + e.getMessage(), e);
                }
            } finally {
                lock.unlock();
            }
        } else {
            throw new DbException("Unsupported database : " + dialectName);
        }
    }

    private static DataSource getDataSource(JdbcTemplate jdbcTemplate) {
        return jdbcTemplate.getDataSource();
    }

    private static String getUrl(DataSource dataSource) {
        Connection conn = null;
        try {
            conn = dataSource.getConnection();
            return conn.getMetaData().getURL();
        } catch (SQLException e) {
            throw new DbException(e);
        } finally {
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    //ignore
                }
            }
        }
    }

    private static String getDialectName(String url) {
        url = url.toUpperCase();
        for (String dialect : DIALECT_MAP.keySet()) {
            if (url.contains(":" + dialect + ":")) {
                return dialect;
            }
        }
        return null;
    }

}
